﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Services.Client;
using System.Data.Services.Common;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class WcfDataServicesDataSourceProviderBase : AsyncDataSourceProvider, ICountRestrictedProvider
  {
    #region Constructors

    protected WcfDataServicesDataSourceProviderBase( DataServiceContext dataServiceContext )
    {
      if( dataServiceContext == null )
        throw new ArgumentNullException( "dataServiceContext" );

      this.DataServiceContext = dataServiceContext;
    }

    internal WcfDataServicesDataSourceProviderBase()
    {
    }

    #endregion

    #region CanSkip Property

    public override bool CanSkip
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilter Property

    public override bool CanFilter
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public sealed override bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public sealed override bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public sealed override bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public override bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public sealed override bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return this.CanCount;
      }
    }

    #endregion

    #region InitialItemCount Property

    public Nullable<long> InitialItemCount
    {
      get;
      set;
    }

    #endregion

    #region DataServiceContext Protected Property

    protected DataServiceContext DataServiceContext
    {
      get;
      private set;
    }

    #endregion

    #region IsQueryLengthLimited Internal Property

    internal override bool IsQueryLengthLimited
    {
      get
      {
        return true;
      }
    }

    #endregion

    protected override IEnumerable<SortDescription> GetPrimaryKeyInfo()
    {
      IEnumerable<object> attributes = this.ElementType.GetCustomAttributes( typeof( DataServiceKeyAttribute ), true );
      foreach( object attribute in attributes )
      {
        DataServiceKeyAttribute keyAttribute = ( DataServiceKeyAttribute )attribute;

        foreach( string keyName in keyAttribute.KeyNames )
        {
          string memberName = string.Intern( keyName );

          yield return new SortDescription( memberName, ListSortDirection.Ascending );
        }
      }
    }

    protected override bool CanSortBy( IEnumerable<SortDescription> sortOrder )
    {
      foreach( SortDescription sd in sortOrder )
      {
        if( sd.PropertyName.Contains( '[' ) )
          return false;
      }

      return base.CanSortBy( sortOrder );
    }

    protected override IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      // Check if we can replace the CountFunction with a call to BeginExecuteCountQuery.
      if( this.CanCount )
      {
        bool mayCount = !dataType.IsNullableType();
        if( ( !mayCount ) && ( this.CanFilter ) )
        {
          // Since the CountFunction may require null values inclusion or exclusion, we need
          // to be able set a filter for null or non-null values.
          List<FilterOperator> filterOperators = this.GetSupportedFilterOperators( dataType ).ToList();

          mayCount = ( filterOperators.Contains( FilterOperator.Equal ) )
                  && ( filterOperators.Contains( FilterOperator.NotEqual ) );
        }

        if( mayCount )
          yield return typeof( CountFunction );
      }

      yield break;
    }

    protected override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      CountFunction countFunction = queryParameters.Function.StatFunction as CountFunction;

      if( countFunction == null )
        // The StatFunction isn't supported.
        throw new NotSupportedException();

      // The CountFunction may be handled by a call to BeginExecuteCountQuery.
      FilterRequest filterRequest = queryParameters.Filter;
      FilterExpressionBase filterExpression = ( filterRequest != null ) ? filterRequest.Expression : null;
      string propertyName = countFunction.GetSourcePropertyNames().Single();
      bool isNullableType = this.GetElementMemberType( propertyName ).IsNullableType();

      // Modify the filter expression to take care of the null value.
      if( isNullableType )
      {
        if( countFunction.IncludeNullValues )
        {
          FilterExpression includeNull = new FilterExpression( propertyName, FilterOperator.Equal, null );

          if( filterExpression != null )
          {
            filterExpression = new OrFilterExpression( includeNull, filterExpression );
          }
          else
          {
            filterExpression = includeNull;
          }
        }
        else
        {
          FilterExpression excludeNull = new FilterExpression( propertyName, FilterOperator.NotEqual, null );

          if( filterExpression != null )
          {
            filterExpression = new AndFilterExpression( excludeNull, filterExpression );
          }
          else
          {
            filterExpression = excludeNull;
          }
        }
      }

      // Create the new FilterRequest for the count query.
      if( filterRequest != null )
      {
        filterRequest = new FilterRequest( filterRequest.Filter, filterExpression );
      }
      else if( filterExpression != null )
      {
        filterRequest = new FilterRequest( filterExpression );
      }
      else
      {
        filterRequest = null;
      }

      // Start the count query.
      CountQueryParameters newQueryParameters = new CountQueryParameters(
                                                  queryParameters.Sort,
                                                  filterRequest,
                                                  null,
                                                  queryParameters.FilterGroup );

      return this.BeginExecuteCountQuery( newQueryParameters, callback, state );
    }

    protected override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      CountQueryResult queryResult = this.EndExecuteCountQuery( asyncResult );

      return new StatQueryResult( new StatResult( queryResult.Count ) );
    }

    internal override IEnumerable<FilterOperator> SupportedFilterOperators( string dataString )
    {
      if( dataString.Contains( '[' ) )
        return null;

      return base.SupportedFilterOperators( dataString );
    }
  }
}
